﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace ParsecParser
{
    public class RealTimeLogReader : IDisposable
    {
        private System.Timers.Timer ReadPollTimer;
        private System.Timers.Timer NetworkPollTimer;
        private FileStream FileStream;
        private StreamReader Reader;
        private long PreviousPosition;
        private bool IsWatchingFile;
        private bool IsWatchingFolder;
        private bool IsNetworkConnected;
        private bool IsReadPolling;
        private bool IsNetworkPolling;
        public bool TickWhenLogIsNotUpdated { get; set; }
        public string FileName { get; set; }
        public string FolderPath { get; set; }
        public long ReadPollMilliseconds { get; set; }
        public long NetworkPollMilliseconds { get; set; }
        public event EventHandler<OnLogUpdatedEventArgs> LogUpdated;
        public event EventHandler<OnNewLogCreatedEventArgs> NewLogCreated;
        public event EventHandler<ErrorEventArgs> Error;
        public event EventHandler RaidTimerTick;
        public DateTime LogFolderLastModified;
        public RealTimeLogReader(string filename, string folderPath, EventHandler<OnLogUpdatedEventArgs> onLogUpdated, EventHandler<OnNewLogCreatedEventArgs> onNewLogCreated, EventHandler<ErrorEventArgs> onError, EventHandler onRaidTimerTick)
        {
            FileName = filename;
            FolderPath = folderPath;
            PreviousPosition = 0;
            ReadPollMilliseconds = 100;
            NetworkPollMilliseconds = 3000;
            LogFolderLastModified = DateTime.MinValue;
            if (onLogUpdated != null)
                LogUpdated += onLogUpdated;
            if (onNewLogCreated != null)
                NewLogCreated += onNewLogCreated;
            if (onError != null)
                Error += onError;
            if (onRaidTimerTick != null)
                RaidTimerTick += onRaidTimerTick;
        }
        public List<string> ReadNewContent()
        {
            if (string.IsNullOrEmpty(FileName))
                throw new ArgumentException("FileName cannot be null");
            if (FileStream != null && String.Compare(FileStream.Name, FileName, true) != 0)
            {
                StopReading();
            }
            if (FileStream == null)
            {
                if (!File.Exists(FileName))
                    throw new Exception("File Not Found");
                FileStream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                Reader = new StreamReader(FileStream, Encoding.GetEncoding("ISO-8859-15"));
                if (PreviousPosition > 0)
                {
                    Reader.DiscardBufferedData();
                    FileStream.Seek(PreviousPosition, SeekOrigin.Begin);
                }
            }
            else if (Reader == null)
            {
                Reader = new StreamReader(FileStream, Encoding.GetEncoding("ISO-8859-15"));
            }
            if (Reader.BaseStream.Position > Reader.BaseStream.Length)
            {
                Reader.BaseStream.Seek(Reader.BaseStream.Length, SeekOrigin.Begin);
            }
            List<string> rval = new List<string>();
            long positionBeforeRead = Reader.BaseStream.Position;
            string newContent = Reader.ReadToEnd();
            if (!string.IsNullOrEmpty(newContent))
            {
                if (!newContent.EndsWith("\r\n"))
                {
                    Reader.DiscardBufferedData();
                    FileStream.Seek(positionBeforeRead, SeekOrigin.Begin);
                    //Debug.WriteLine(string.Format("{0} - Read didnt End with newline", DateTime.Now.ToString("hh:mm:ss.ffff")));
                }
                else
                {
                    rval = new List<string>(newContent.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries));
                    //Debug.WriteLine(string.Format("{0} - Good Read!", DateTime.Now.ToString("hh:mm:ss.ffff")));
                }
            }
            PreviousPosition = Reader.BaseStream.Position;
            return rval;
        }
        public void StartWatchingFolder()
        {
            IsWatchingFolder = true;
            if (string.IsNullOrEmpty(FolderPath))
                throw new ArgumentException("FolderPath cannot be null");
            if (!Directory.Exists(FolderPath))
                throw new ArgumentException("Folder does not exist");
            if (ReadPollTimer == null || !IsWatchingFile)
            {
                StartReadPolling();
            }
        }
        public void StopWatchingFolder()
        {
            IsWatchingFolder = false;
            if (ReadPollTimer != null && !IsWatchingFile)
            {
                StopReadPolling();
            }
        }
        public void StartWatchingFile()
        {
            IsWatchingFile = true;
            if (string.IsNullOrEmpty(FileName))
                throw new ArgumentException("FileName cannot be null");
            if (!File.Exists(FileName))
                throw new ArgumentException("File does not exist");
            if (ReadPollTimer == null || !IsWatchingFolder)
            {
                StartReadPolling();
            }
        }
        public void StopWatchingFile()
        {
            IsWatchingFile = false;
            if (ReadPollTimer != null && !IsWatchingFolder)
            {
                StopReadPolling();
            }
        }

        public void StartNetworkTimer()
        {
            IsNetworkConnected = true;
            StartNetworkPolling();
        }
        public void StopNetworkTimer()
        {
            IsNetworkConnected = false;
            StopNetworkPolling();
        }
        private void StopReadPolling()
        {
            if (IsReadPolling)
            {
                ReadPollTimer.AutoReset = false;
                ReadPollTimer.Elapsed -= ReadTimer_Elapsed;
                ReadPollTimer.Stop();
                IsReadPolling = false;
                ReadPollTimer.Dispose();
            }
        }
        private void StartReadPolling()
        {
            if (!IsReadPolling)
            {
                ReadPollTimer = new System.Timers.Timer(ReadPollMilliseconds);
                ReadPollTimer.AutoReset = false;
                ReadPollTimer.Elapsed += new System.Timers.ElapsedEventHandler(ReadTimer_Elapsed);
                ReadPollTimer.Start();
                IsReadPolling = true;
            }
        }
        private void StartNetworkPolling()
        {
            if (!IsNetworkPolling)
            {
                NetworkPollTimer = new System.Timers.Timer(NetworkPollMilliseconds);
                NetworkPollTimer.AutoReset = false;
                NetworkPollTimer.Elapsed += new System.Timers.ElapsedEventHandler(NetworkTimer_Elapsed);
                NetworkPollTimer.Start();
                IsNetworkPolling = true;
            }
        }
        private void StopNetworkPolling()
        {
            if (IsNetworkPolling)
            {
                NetworkPollTimer.AutoReset = false;
                NetworkPollTimer.Elapsed -= NetworkTimer_Elapsed;
                NetworkPollTimer.Stop();
                IsNetworkPolling = false;
                NetworkPollTimer.Dispose();
            }
        }
        public void StopReading()
        {
            if (Reader != null)
            {
                Reader.Close();
                Reader = null;
            }
            else if (FileStream != null)
                FileStream.Close();
            FileStream = null;
            PreviousPosition = 0;
        }
        private void ReadTimer_Elapsed(object source, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                if (IsWatchingFile)
                {
                    List<string> newLogLines = ReadNewContent();
                    if (newLogLines.Count > 0 || TickWhenLogIsNotUpdated)
                    {
                        OnLogUpdated(new OnLogUpdatedEventArgs() { NewLines = newLogLines });
                    }
                }
                if (IsWatchingFolder)
                {
                    string newFile = GetNewestFile();
                    if (newFile != FileName)
                    {
                        OnNewLogCreated(new OnNewLogCreatedEventArgs() { FileName = newFile });
                    }
                }
                //OnRaidTimerTick(new EventArgs());
                if (!IsWatchingFile && !IsWatchingFolder)
                {
                    IsReadPolling = true;
                    StopReadPolling();
                }
            }
            catch (Exception ex)
            {
                if (!OnError(new ErrorEventArgs(ex)))
                {
                    throw;
                }
            }
            if (IsReadPolling)
                ReadPollTimer.Start();
        }
        private void NetworkTimer_Elapsed(object source, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                if (IsNetworkConnected)
                    OnRaidTimerTick(new EventArgs());
                if (!IsNetworkConnected)
                {
                    IsNetworkPolling = true;
                    StopNetworkPolling();
                }
            }
            catch (Exception ex)
            {
                if (!OnError(new ErrorEventArgs(ex)))
                {
                    throw;
                }
            }
            NetworkPollTimer.Interval = this.NetworkPollMilliseconds;
            if (IsNetworkPolling)
                NetworkPollTimer.Start();
        }
        protected virtual void OnLogUpdated(OnLogUpdatedEventArgs e)
        {
            EventHandler<OnLogUpdatedEventArgs> handler = LogUpdated;
            if (handler != null)
            {
                handler(this, e);
            }
            else if (IsWatchingFile)
            {
                StopWatchingFile();
                StopWatchingFolder();
                StopReading();
            }
        }
        protected virtual void OnNewLogCreated(OnNewLogCreatedEventArgs e)
        {
            EventHandler<OnNewLogCreatedEventArgs> handler = NewLogCreated;
            if (handler != null)
            {
                handler(this, e);
            }
            else if (IsWatchingFolder)
            {
                StopWatchingFile();
                StopWatchingFolder();
                StopReading();
            }
        }
        protected virtual bool OnError(ErrorEventArgs e)
        {
            EventHandler<ErrorEventArgs> handler = Error;
            if (handler != null)
            {
                handler(this, e);
                return true;
            }
            else
                return false;
        }
        protected virtual bool OnRaidTimerTick(EventArgs e)
        {
            EventHandler handler = RaidTimerTick;
            if (handler != null)
            {
                handler(this, e);
                return true;
            }
            else
                return false;
        }

        public void Dispose()
        {
            StopWatchingFile();
            StopWatchingFolder();
            StopNetworkTimer();
            StopReading();
        }

        public static string FindNewestFile(string folderPath)
        {
            var d = new DirectoryInfo(folderPath);
            var newFile = d.GetFiles().OrderByDescending(x => x.LastWriteTime).FirstOrDefault();
            if (newFile != null)
                return newFile.FullName;
            else
                return null;
        }
        public string GetNewestFile()
        {
            DateTime lastModded = Directory.GetLastWriteTime(FolderPath);
            if (lastModded != LogFolderLastModified)
            {
                LogFolderLastModified = lastModded;
                return FindNewestFile(FolderPath);
            }
            else
                return FileName;
        }
    }
    public class OnLogUpdatedEventArgs : EventArgs
    {
        public List<string> NewLines { get; set; }
    }
    public class OnNewLogCreatedEventArgs : EventArgs
    {
        public string FileName { get; set; }
    }
}
